import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

/**
 * Resulting probability table for a Markov state.
 */
public class MarkovPTable implements Serializable
{
	private static final long serialVersionUID = -2282818558710395908L;
	private MarkovStateSet key;
	private int totalInstances;
	private HashMap<MarkovState, MarkovPTableEntry> probabilityTable;
	private List<MarkovPTableEntry> sortedPTable;
	
	
	/**
	 * Constructor for MarkovPTable. Takes a MarkovState that will
	 * act as the key for the HashMap. 
	 * 
	 * @param tokenValue <code>MarkovState</code>
	 */
	public MarkovPTable(MarkovStateSet tokenValue)
	{
		if(MarkovModel.VERBOSITY == 3)
			System.out.println("ENTER: MarkovPTable.MarkovPTable ("+tokenValue+")");
		
		key = tokenValue;
		totalInstances = 0;		
		probabilityTable = 
			new HashMap<MarkovState, MarkovPTableEntry>();
		sortedPTable = new ArrayList<MarkovPTableEntry>();

		if(MarkovModel.VERBOSITY == 3)
			System.out.println("EXIT: MarkovPTable.MarkovPTable");
	}
	
	/**
	 * Copy constructor for MarkovPTable. 
	 * @param pTable
	 */
	public MarkovPTable(MarkovPTable pTable)
	{
		if(MarkovModel.VERBOSITY == 3)
			System.out.println("ENTER: MarkovPTable.MarkovPTable ("+pTable+")");
		
		key = pTable.key;
		totalInstances = pTable.totalInstances;
		probabilityTable = 
			new HashMap<MarkovState, MarkovPTableEntry>();
		sortedPTable = new ArrayList<MarkovPTableEntry>();
		
		for(MarkovPTableEntry entry : sortedPTable)
		{
			sortedPTable.add(entry);
			probabilityTable.put(entry.getState(), entry);
		}

		if(MarkovModel.VERBOSITY == 3)
			System.out.println("EXIT: MarkovPTable.MarkovPTable");
	}
	
	/**
	 * Returns the key for this MarkovPTable. The key is the 
	 * MarkovState that we are trying to predict for.
	 * 
	 * @return <code>MarkovState</code>
	 */
	public MarkovStateSet getKey()
	{
		if(MarkovModel.VERBOSITY == 3)
			System.out.println("ENTER: MarkovPTable.getKey ()");
		
		if(MarkovModel.VERBOSITY == 3)
			System.out.println("EXIT: MarkovPTable.getKey ("+key+")");
		return key;
	}
	
	/**
	 * Increases the frequency for the state. If we are in the 
	 * MarkovState specified by the key and we see this state 
	 * occur, then this function should be called.  
	 * 
	 * @param state <code>MarkovState</code>
	 */
	public void addInstance(MarkovState state)
	{
		if(MarkovModel.VERBOSITY == 3)
			System.out.println("ENTER: MarkovPTable.addInstance ("+state+")");
		if(probabilityTable.containsKey(state))
		{
			MarkovPTableEntry entry = probabilityTable.get(state);
			entry.increaseFrequency();
			
		}
		else
		{
			MarkovPTableEntry newEntry = new MarkovPTableEntry(state, 1); 
			probabilityTable.put(state, newEntry);
			sortedPTable.add(newEntry);
		}
		
		resort();
		totalInstances++;
		
		if(MarkovModel.VERBOSITY == 3)
			System.out.println("EXIT: MarkovPTable.addInstance");
	}
	
	/**
	 * Returns the sorted PTable
	 * 
	 * @return <code>List<MarkovPTableEntry</code>
	 */
	public List<MarkovPTableEntry> getMarkovPTableEntries()
	{
		if(MarkovModel.VERBOSITY == 3)
			System.out.println("ENTER: MarkovPTable.getMarkovPTableEntries ()");

		if(MarkovModel.VERBOSITY == 3)
			System.out.println("EXIT: MarkovPTable.getMarkovPTableEntries");
		return sortedPTable; 
	}
	
	public Set<MarkovState> getMarkovStates()
	{
		return probabilityTable.keySet();
	}
	/**
	 * Computes the probability
	 * 
	 * @param mState <code>MarkovState</code>
	 * @return
	 */
	public double getProbability(MarkovState mState)
	{
		if(MarkovModel.VERBOSITY == 3)
			System.out.println("ENTER: MarkovPTable.getProbability ("+mState+")");
		double probability = 0.0;
		
		if(probabilityTable.containsKey(mState))
		{
			int frequency = 
				probabilityTable.get(mState).getFrequency();
			
			probability = (1.0 * frequency) / totalInstances;
		}

		if(MarkovModel.VERBOSITY == 3)
			System.out.println("EXIT: MarkovPTable.getProbability ("+probability+")");
		return probability;
	}
	
	/**
	 * Resorts the sortedPTable.
	 */
	private void resort()
	{
		if(MarkovModel.VERBOSITY == 3)
			System.out.println("ENTER: MarkovPTable.resort ()");
		
		Collections.sort(sortedPTable);

		if(MarkovModel.VERBOSITY == 3)
			System.out.println("EXIT: MarkovPTable.resort");
	}
	public String toString()
	{
		String result = "MarkovPTable@"+this.hashCode()+"[\n";
		for(MarkovState key : probabilityTable.keySet())
		{
			result+="-key="+key+"\n"
					+"--"+probabilityTable.get(key)+"\n";
		}
		result+="]";
		return result;
	}

	public void updateMarkovState(MarkovState iState, MarkovState markovState)
	{
		MarkovPTableEntry pTableEntry = probabilityTable.remove(iState);
		probabilityTable.put(markovState, pTableEntry);
		
		pTableEntry.setState(markovState);
	}
}
